package cn.bugstack.mybatis.executor;

import cn.bugstack.mybatis.cache.Cache;
import cn.bugstack.mybatis.cache.CacheKey;
import cn.bugstack.mybatis.cache.TransactionalCacheManager;
import cn.bugstack.mybatis.mapping.BoundSql;
import cn.bugstack.mybatis.mapping.MappedStatement;
import cn.bugstack.mybatis.session.ResultHandler;
import cn.bugstack.mybatis.session.RowBounds;
import cn.bugstack.mybatis.transaction.Transaction;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.List;

/**
 * @author xingshujing
 */
public class CachingExecutor implements Executor {

    private Logger logger = LoggerFactory.getLogger(CachingExecutor.class);

    private final Executor delegate;

    private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws Exception {
        CacheKey cacheKey = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    }
 
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws Exception {
        return 0;
    }

    @Override
    public void commit(boolean required) throws SQLException {
        delegate.commit(required);
        tcm.commit();
    }

    @Override
    public Transaction getTransaction() {
        return null;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws Exception {
        Cache cache = ms.getCache();
        if (cache != null) {
            // 1. 先查二级缓存
            if (ms.isUseCache() && resultHandler == null) {
                List<E> list = (List<E>)tcm.getObject(cache, key);
                // 打印调试日志，记录二级缓存获取数据
                if (logger.isDebugEnabled() && list!=null) {
                    logger.debug("二级缓存命中：key: {}; value: {}", JSON.toJSONString(key),JSON.toJSONString(list));
                }
                // 2. 如果二级缓存没有命中，那么就查数据库
                if (list == null) {
                    list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
                    // 查库之后，放入二级缓存
                    tcm.putObject(cache, key, list);
                }
                // 3.如果二级缓存命中
                return list;
            }
        }
        // mapper 没有设置二级缓存，那么就走SimpleExecutor的一级缓存
        return delegate.query(ms,parameter,rowBounds,resultHandler,key,boundSql);
    }

    @Override
    public void clearLocalCache() {
        delegate.clearLocalCache();
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        try {
            delegate.rollback(required);
        }finally {
            if (required) {
                tcm.rollback();
            }
        }
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            if (forceRollback) {
                tcm.rollback();
            } else {
                tcm.commit();
            }
        }finally {
            // close SimpleExecutor
            delegate.close(forceRollback);
        }
    }

    @Override
    public void setExecutorWrapper(CachingExecutor cachingExecutor) {
        throw new UnsupportedOperationException("This method should not be called");
    }
}
